package com.hbsd.paopaobackend.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.hbsd.paopaobackend.common.ErrorCode;
import com.hbsd.paopaobackend.common.ResultUtils;
import com.hbsd.paopaobackend.exception.BusinessException;
import com.hbsd.paopaobackend.mapper.UserMapper;
import com.hbsd.paopaobackend.model.domain.User;
import com.hbsd.paopaobackend.model.request.PageInfo;
import com.hbsd.paopaobackend.model.request.User.UserUpdateTagRequest;
import com.hbsd.paopaobackend.model.vo.UserVO;
import com.hbsd.paopaobackend.service.UserService;
import com.hbsd.paopaobackend.utils.AIgorithumUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.util.Pair;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.hbsd.paopaobackend.constant.UserConstant.ADMIN_ROLE;
import static com.hbsd.paopaobackend.constant.UserConstant.USER_LOGIN_STATE;

/**
 * 用户服务实现类
 *
 * @author zzh
 * @description 针对表【user(用户表)】的数据库操作Service实现

 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private UserMapper userMapper;

    /**
     * RedisTemplate
     */
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /*
     * 盐值
     */

    private static final String SALT = "zzh";


    /**
     * 用户注册
     *
     * @param userAccount   用户账号
     * @param userPassword  用户密码
     * @param checkPassword 校验密码
     * @param planetCode    星球编号
     * @return
     */
    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode) {
        // 1、校验(是否为空/null/空字符串)
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) {
//            return -1;
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
        }
        // 用户账号长度小于2
        if (userAccount.length() < 2) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号长度不能小于2位");
        }
        // 密码长度小于6
        if (userPassword.length() < 6 || checkPassword.length() < 6) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不能小于6位");
        }

        // 星球编号大于5位
        if (planetCode.length() > 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号不能大于5位");
        }

        // 账号不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？\\s]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            return -1;
        }

        // 账号不能重复
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        long count = userMapper.selectCount(queryWrapper);
        // 用户账号重复
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在");
        }

        // 星球账号不能重复
        QueryWrapper<User> queryWrapperPlanet = new QueryWrapper<>();
        queryWrapperPlanet.eq("planetCode", planetCode);
        long countPlanet = userMapper.selectCount(queryWrapperPlanet);
        if (countPlanet > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号已存在");
        }


        // 密码和校验密码相同
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码和校验密码不一致");
        }


        // 2、加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 3、保存到数据库
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setPlanetCode(planetCode);
        boolean saveResult = this.save(user);
        // 4、保存失败
        if (!saveResult) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "注册失败");
        }

        return user.getId();


    }

    /**
     * 用户登录
     *
     * @param userAccount  用户账号
     * @param userPassword 用户密码
     * @param request      请求对象
     * @return
     */

    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1、校验(是否为空/null/空字符串)
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
        }
        // 用户账号长度小于2
        if (userAccount.length() < 2) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号长度不能小于2位");
        }
        // 密码长度小于6
        if (userPassword.length() < 6) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不能小于6位");
        }

        // 账号不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？\\s]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号不能包含特殊字符");
        }


        // 2、加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());

        // 账号不能重复
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        queryWrapper.eq("userPassword", encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        // 用户不存在或密码错误
        if (user == null) {
            log.info("user login failed, userAccount cannot match userPassword");
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号或密码错误");
        }

        // 3、用户脱敏
        User safetyUser = getSafetyUser(user);
        // 4、记录用户的登陆状态
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);


        return safetyUser;
    }

    /**
     * 用户列表查询
     *
     * @param user
     * @return
     */
    @Override
    public List<User> searchUsers(User user) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        // 判断用户名是否为空
        if (StringUtils.isNotBlank(user.getUsername())) {
            queryWrapper.like("username", user.getUsername());
        }
        // 判断用户账号是否为空
        if (StringUtils.isNotBlank(user.getUserAccount())) {
            queryWrapper.like("userAccount", user.getUserAccount());
        }
        // 判断手机号是否为空
        if (StringUtils.isNotBlank(user.getPhone())) {
            queryWrapper.like("phone", user.getPhone());
        }
        // 判断用户状态是否为空
        if (user.getUserStatus() != null) {
            queryWrapper.eq("userStatus", user.getUserStatus());
        }
        // 判断用户角色是否为空
        if (user.getUserRole() != null) {
            queryWrapper.eq("userRole", user.getUserRole());
        }

        // 判断 createTime 是否存在，按指定时间范围过滤
        if (user.getCreateTime() != null) {
            // 将 Date 转换为 LocalDateTime
            LocalDateTime createDateTime = user.getCreateTime().toInstant()
                    .atZone(ZoneId.systemDefault())
                    .toLocalDateTime();

            // 设置开始时间为传递的时间
            LocalDateTime startTime = createDateTime;
            // 设置结束时间为当天的 23:59:59
            LocalDateTime endTime = createDateTime.toLocalDate().atTime(23, 59, 59);

            queryWrapper.between("createTime", startTime, endTime);
        }

        // this.baseMapper: 调用父类的 mapper 对象
        return this.baseMapper.selectList(queryWrapper).stream()
                // 脱敏用户信息
                .map(this::getSafetyUser)
                .collect(Collectors.toList());

    }

    /**
     * 推荐用户数据(使用了redis缓存)
     *
     * @param pageInfo
     * @param request
     * @return
     */
    @Override
    public Page<User> recommendUsers(PageInfo pageInfo, HttpServletRequest request) {
        // 获取登录用户信息的id
        Long userId = this.getLoginUser(request).getId();
        // 如果有缓存，直接读缓存
        String redisKey = String.format("paopao:user:recommend:%s", userId);
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        Page<User> userPage = (Page<User>) valueOperations.get(redisKey);

        // 有缓存，直接返回
        if (userPage != null) {
            return ResultUtils.success(userPage).getData();
        }
        // 没有缓存，分页查询
        // 分页查询
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        userPage = this.page(new Page<>(pageInfo.getPageNum(), pageInfo.getPageSize()), queryWrapper);

        // 缓存数据
        try {
            // 设置了过期时间: 10000ms = 10s
            // 1天后过期
            valueOperations.set(redisKey, userPage, 1, TimeUnit.DAYS);
        } catch (Exception e) {
            log.info("redis set key error", e);
        }
        return userPage;
    }

    /**
     * 用户脱敏
     *
     * @param originUser
     * @return 返回脱敏信息
     */
    @Override
    public User getSafetyUser(User originUser) {
        if (originUser == null) return null;
        User safetyUser = new User();
        safetyUser.setId(originUser.getId());
        safetyUser.setUsername(originUser.getUsername());
        safetyUser.setUserAccount(originUser.getUserAccount());
        safetyUser.setAvatarUrl(originUser.getAvatarUrl());
        safetyUser.setGender(originUser.getGender());
        safetyUser.setPhone(originUser.getPhone());
        safetyUser.setEmail(originUser.getEmail());
        safetyUser.setUserStatus(originUser.getUserStatus());
        safetyUser.setUserRole(originUser.getUserRole());
        safetyUser.setPlanetCode(originUser.getPlanetCode());
        safetyUser.setCreateTime(originUser.getCreateTime());
        safetyUser.setTags(originUser.getTags());
        safetyUser.setUpdateTime(originUser.getUpdateTime());
        safetyUser.setRemark(originUser.getRemark());


        return safetyUser;
    }

    /**
     * 用户注销
     *
     * @param request
     */
    @Override
    public int userLogout(HttpServletRequest request) {
        // 移除session
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return 1;
    }

    // 正则表达式，用于匹配常见的中国大陆手机号码格式
    private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";

    /**
     * 判断手机号是否合法
     *
     * @param phoneNumber 手机号码
     * @return 如果手机号符合正则表达式，返回 true；否则返回 false
     */
    public static boolean isValidPhoneNumber(String phoneNumber) {
        if (phoneNumber == null) {
            return false;
        }
        return phoneNumber.matches(PHONE_REGEX);
    }

    // 正则表达式，用于匹配常见的邮箱格式
    private static final String EMAIL_REGEX = "^[\\w!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";

    /**
     * 判断邮箱是否合法
     * @param email 邮箱地址
     * @return 如果邮箱地址符合正则表达式，返回 true；否则返回 false
     */
    public static boolean isValidEmail(String email) {
        if (email == null) {
            return false;
        }
        return email.matches(EMAIL_REGEX);
    }

    @Override
    public int userListUpdate(User user, User loginUser) {
        // user: 要修改的用户信息
        // loginUser: 用户的登录信息
        Long userId = user.getId();
        Long loginUserId = loginUser.getId();


        if (userId <= 0 || userId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在");
        }
        // 如果既不是管理员,并且传入的用户id和登录时候的用户id不一致，那么是不可以进行修改的
//        if(!isAdmin(loginUser) && userId != loginUserId) {
        if (!userId.equals(loginUserId)) {
            throw new BusinessException(ErrorCode.NO_AUTH, "无权限修改");
        }

        // 判断手机号是否合法
        String phone = user.getPhone();
        if (phone != null) {
            if (!isValidPhoneNumber(phone)) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "手机号不合法");
            }
        }

        // 判断邮箱是否合法
        String email = user.getEmail();
        if(email!=null) {
            if(!isValidEmail(email)) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱不合法");
            }
        }

        // 修改用户信息
        User oldUser = userMapper.selectById(userId);
        if (oldUser == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在");
        }
        int result = userMapper.updateById(user);
        return result;
    }

    /**
     * 用户列表删除
     *
     * @param id
     * @return
     */
    @Override
    public Integer userListDelete(Long id) {
        int result = userMapper.deleteById(id);
        return result;
    }

    /**
     * 用户头像上传
     *
     * @param file
     * @param request
     * @return
     */
    @Override
    public String saveFile(MultipartFile file, HttpServletRequest request) {
        String originalName = file.getOriginalFilename();

        // 检查文件名是否为空，并确保是图片文件
        if (originalName == null || !originalName.matches(".*\\.(png|jpg|jpeg|gif|bmp)$")) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型不支持，只允许上传图片文件");
        }


        // 获取文件扩展名
        String fileExtension = originalName.substring(originalName.lastIndexOf("."));

        // 文件保存目录路径
        String format = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
        String uploadDir = "D:/uploads" + format;
        File folder = new File(uploadDir);
        if (!folder.exists()) {
            folder.mkdirs();
        }

        // 生成文件名并加上原始扩展名
        String newName = UUID.randomUUID().toString() + fileExtension;
        try {
            file.transferTo(new File(folder, newName));

            // 生成文件的访问 URL
            String contextPath = request.getContextPath();
            String fileUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
                    + contextPath + format + newName;

            return fileUrl;
        } catch (IOException e) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "上传失败");
        }
    }

    /**
     * 根据标签搜索用户
     *
     * @param tagNameList 用户拥有的标签
     * @return
     */
    @Override
    public List<User> searchUsersByTags(List<String> tagNameList) {
        // 标签不能为空
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "标签不能为空");
        }

//        方法二、在内存中查找(不可以)
        // 1、查询所有用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> users = userMapper.selectList(queryWrapper);
        Gson gson = new Gson();

        // 2、在内存中判断是否包含要求的标签
        List<User> userCollectList = users.stream().filter(user -> {
            String tagsStr = user.getTags();

            // 字符串 -> List(序列化)
            Set<String> tempTagNameSet = gson.fromJson(tagsStr, new TypeToken<Set<String>>() {
            }.getType());
            // 防止空指针异常
            // Optional.ofNullable(tempTagNameSet) 就是用来判断它为不为空
            // 如果 tempTagNameSet 没空，就给他自动赋值一个set
            tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>());

            // List -> 字符串(反序列化)
            // gson.toJson(tempTagNameList);

            for (String tagName : tagNameList) {
                if (!tempTagNameSet.contains(tagName)) {
                    return false;
                }
            }

            return true;
        }).map(this::getSafetyUser).collect(Collectors.toList());

        return userCollectList;


    }

    /**
     * 根据标签搜索用户(SQL实现)
     *
     * @param tagNameList
     * @return
     */
    @Override
    public List<User> searchUsersByTagsBySQL(List<String> tagNameList) {
        // 标签不能为空
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "标签不能为空");
        }

        // 构造查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("*, (" +
                        tagNameList.stream()
                                .map(tagName -> "CASE WHEN tags LIKE '%" + tagName + "%' THEN 1 ELSE 0 END")
                                .collect(Collectors.joining(" + ")) +
                        ") AS match_count")
                .orderByDesc("match_count");

        queryWrapper.and(wrapper -> {
            for (String tagName : tagNameList) {
                wrapper.or().like("tags", tagName);
            }
        });

        // 查询用户
        List<User> users = userMapper.selectList(queryWrapper);

        // 脱敏数据
        return users.stream()
                .map(this::getSafetyUser)
                .collect(Collectors.toList());
    }


    /**
     * 获取当前登录用户信息
     *
     * @param request
     * @return
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        if (request == null) {
            return null;
        }

        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);

        // 判断用户是否登录
        if (userObj == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN, "用户未登录");
        }

        // 强转
        User user = (User) userObj;
        return user;
    }


    /**
     * 是否为管理员
     *
     * @param request
     * @return
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {

        // 仅管理员可查询
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);

        User user = (User) userObj;
        return user != null && user.getUserRole() == ADMIN_ROLE;
    }


    /**
     * 是否为管理员
     *
     * @param loginUser
     * @return
     */
    @Override
    public boolean isAdmin(User loginUser) {
        return loginUser != null && loginUser.getUserRole() == ADMIN_ROLE;
    }

    /**
     * 匹配用户
     *
     * @param num
     * @param loginUser
     * @return
     */
    @Override
    public List<User> matchUsers(long num, User loginUser) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 只查询id 和 tags字段
        queryWrapper.select("id", "tags");
        // 匹配不为空的标签(过滤掉标签为空的用户)
        queryWrapper.isNotNull("tags");
        List<User> userList = this.list(queryWrapper);
        String tags = loginUser.getTags();

        Gson gson = new Gson();
        List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {
        }.getType());

        // 用户列表下标 => 相似度
//        SortedMap<Integer, Long> indexDistanceMap = new TreeMap<>();
        List<Pair<User, Long>> list = new ArrayList<>();
        // 依次计算所有用户和当前用户的相似度
        for (int i = 0; i < userList.size(); i++) {
            User user = userList.get(i);
            String userTags = user.getTags();
            // 无标签 或者 为当前用户自己
            if (StringUtils.isBlank(userTags) || Objects.equals(user.getId(), loginUser.getId())) {
                // throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户标签不能为空");
                continue;
            }
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
            }.getType());


            // 计算两两标签相似度匹配
            long distance = AIgorithumUtils.minDistanceTags(tagList, userTagList);
            // 相似度越大，排名越靠前
            // indexDistanceMap.put(i, distance);
            list.add(new Pair<>(user, distance));

        }

        // 按照编辑距离由小到大排序(升序)
        List<Pair<User, Long>> topUserPairList = list.stream().sorted((a, b) -> (int) (a.getValue() - b.getValue())).limit(num).collect(Collectors.toList());

        // 原本顺序的 userId列表
        List<Long> userIdList = topUserPairList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());

        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        // 查询所有 id 值属于 userIdList 中的用户记录。
        userQueryWrapper.in("id", userIdList);

        // 1、3、2
        // User1、User2、User3
        // 1 => User1、2 => User2、3 => User3

        // 根据 user表中的数据，查询与之对应 userIdList中id的用户，并进行脱敏
        Map<Long, List<User>> userIdUserListMap = this.list(userQueryWrapper).stream().map(user -> getSafetyUser(user))
                .collect(Collectors.groupingBy(User::getId));


        List<User> finalUserList = new ArrayList<>();
        // 遍历 id列表
        for (Long userId : userIdList) {
            // 获取对应 id用户列表，添加到最终结果中
            System.out.println("userIdUserListMap = " + userIdUserListMap.get(userId));
            finalUserList.add(userIdUserListMap.get(userId).get(0));
        }


        return finalUserList;
    }

    /**
     * 用户列表标签更新(欢迎页面)
     *
     * @param userUpdateTagRequest
     * @param loginUser
     * @return
     */
    @Override
    public int userUserListUpdateTag(UserUpdateTagRequest userUpdateTagRequest, User loginUser) {

        // loginUser: 用户的登录信息
        Long loginUserId = loginUser.getId();


        if (loginUserId <= 0 || loginUserId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在");
        }

        // 修改用户信息
        User oldUser = userMapper.selectById(loginUserId);
        if (oldUser == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在");
        }

        User user = new User();
        user.setId(loginUserId);
        user.setUpdateTime(new Date());

        try {
            BeanUtils.copyProperties(user, userUpdateTagRequest);
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数错误");
        }
        int result = userMapper.updateById(user);
        return result;
    }

    /**
     * 将标签字符串转换为数组
     * @param loginUser
     * @return
     */
    @Override
    public UserVO changeTagsToArray(User loginUser) {
        UserVO userVO = new UserVO();

        BeanUtil.copyProperties(loginUser, userVO ,"tags");
        userVO.setTags(Objects.nonNull(loginUser.getTags()) ? JSONUtil.parseArray(loginUser.getTags()).toList(String.class): new ArrayList<>());

        return userVO;
    }

}



